﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Silent.Data.Converters
{
    internal class DefaultConverter<T, U> : IConvertable<T, U>
        where U : new()
    {
        #region Copy Methods

        private void CopyElement(object source, object dist)
        {
            if (source == null || dist == null)
                return;

            Type sourceType = source.GetType();
            Type distType = dist.GetType();

            if (sourceType.IsArray)
            {
                CopyArrayElements(source as Array, dist as Array, sourceType.GetElementType());
            }
            else if (sourceType.GetInterface("ICollection", true) != null)
            {
                CopyCollectionElements(source as ICollection, dist as ICollection, sourceType.GetElementType());
            }
            else if (sourceType.IsClass || sourceType.IsInterface)
            {
                CopyElementProperties(source, dist, sourceType, distType);
                CopyElementFields(source, dist, sourceType, distType);
            }
            else
            {
                source = dist;
            }
        }
        private void CopyElementProperties(object source, object dist, Type sourceType, Type distType)
        {
            const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty;

            foreach (PropertyInfo sourceProperty in sourceType.GetProperties(bindingFlags))
            {
                PropertyInfo distProperty = distType.GetProperty(sourceProperty.Name, bindingFlags);
                if (distProperty != null && distProperty.CanWrite)
                {
                    object sourceValue = sourceProperty.GetValue(source, bindingFlags, null, null, null);
                    if (sourceValue == null)
                        continue;

                    Type valueType = sourceValue.GetType();
                    object distValue = HandleTypeCopying(sourceValue, valueType);
                    distProperty.SetValue(dist, distValue, null);
                }
            }
        }

        private void CopyElementFields(object source, object dist, Type sourceType, Type distType)
        {
            foreach (FieldInfo field in sourceType.GetFields())
            {
                FieldInfo distField = distType.GetField(field.Name);
                if (distField != null)
                {
                    object value = field.GetValue(source);
                    distField.SetValue(dist, value);
                }
            }
        }
        private void CopyArrayElements(Array source, Array dist, Type elementType)
        {
            var distList = new List<object>();

            foreach (object sourceElementValue in source)
            {
                object distElementValue = HandleTypeCopying(sourceElementValue, elementType);
                distList.Add(distElementValue);
            }

            distList.ToArray().CopyTo(dist, 0);
        }
        private void CopyCollectionElements(ICollection source, ICollection dist, Type elementType)
        {
            if (elementType == null)
                return;

            var distList = new List<object>();

            foreach (object sourceElementValue in source)
            {
                object distElementValue = HandleTypeCopying(sourceElementValue, elementType);
                distList.Add(distElementValue);
            }

            //distList.ToArray().CopyTo(dist, 0);
        }
        private object HandleTypeCopying(object source, Type distType)
        {
            if (distType.IsArray)
            {
                var dist = Activator.CreateInstance(distType, new object[] {(source as Array).Length}) as Array;
                CopyArrayElements(source as Array, dist, distType.GetElementType());
                return dist;
            }
            if (distType.GetInterface("ICollection", true) != null)
            {
                var dist =
                    Activator.CreateInstance(distType, new object[] {(source as ICollection).Count}) as ICollection;
                CopyCollectionElements(source as ICollection, dist, distType.GetElementType());
                return dist;
            }
            if (distType.IsClass || distType.IsInterface)
            {
                object dist = Activator.CreateInstance(distType);
                CopyElement(source, dist);
                return dist;
            }
            else
            {
                object dist = source;
                return dist;
            }
        }

        #endregion

        #region Implementation of IConvertable

        public U Convert(T source)
        {
            if (source == null)
                return default(U);

            var dist = new U();
            CopyElement(source, dist);
            return dist;
        }

        #endregion
    }
}